﻿// Inner Fire 游戏引擎库
// DirectX工具类
//
// Copyright (c) 2023 by 尔西大帝. All rights reserved.
//
// 用于DirectX的一些便捷操作
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2024-11-16

#include "dx_utils.h"
#include <cmath>
#include <comdef.h>
#include <float.h>
#include <fstream>

using namespace DirectX;

namespace ifire {

namespace dxutils {

static std::function<void(std::string)> output_function;

UINT calc_constant_buffer_size(UINT origin_byte_size) {
  return (origin_byte_size + 255) & ~255;
}

void set_output_stream(std::function<void(std::string)> func) {
  output_function = func;
}

void log(const std::string& msg) {
  if (output_function) {
    output_function(msg);
  } else {
    OutputDebugStringA(msg.c_str());
  }
}

ComPtr<ID3DBlob> load_shader(const std::string& filename) {
  std::ifstream fin(filename, std::ios::binary);

  // 确定文件的大小
  fin.seekg(0, std::ios_base::end);
  std::ifstream::pos_type size = (int)fin.tellg();
  fin.seekg(0, std::ios_base::beg);

  // 创建Blob
  ComPtr<ID3DBlob> blob;
  D3DCreateBlob(size, blob.GetAddressOf());

  // 载入到blob中
  fin.read((char*)blob->GetBufferPointer(), size);
  fin.close();

  return blob;
}

ComPtr<ID3DBlob> compile_shader(const std::string& filename,
    const D3D_SHADER_MACRO* defines, const std::string& entry_point,
    const std::string& target) {
  // 如果处于调式模式，则使用调式根据
  UINT compile_flags = 0;
#if defined(DEBUG) || defined(_DEBUG)
  compile_flags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif

  // 转化为宽字符
  auto filename_w = AnsiToWString(filename);

  // 编译Shader
  ComPtr<ID3DBlob> byte_code = nullptr;
  ComPtr<ID3DBlob> errors;
  HRESULT hr = D3DCompileFromFile(filename_w.c_str(), defines,
      D3D_COMPILE_STANDARD_FILE_INCLUDE, entry_point.c_str(), target.c_str(),
      compile_flags, 0, &byte_code, &errors);

  // 如果有错误则输出
  if (errors != nullptr) {
    log((char*)errors->GetBufferPointer());
  }
  // 常规异常处理
  HASSERT(hr);

  return byte_code;
}

XMVECTOR spherical_to_cartesian(float radius, float theta, float phi) {
  return DirectX::XMVectorSet(radius * sinf(phi) * cosf(theta),
      radius * cosf(phi), radius * sinf(phi) * sinf(theta), 1.0f);
}

bool is_key_down(int vkeyCode) {
  return (GetAsyncKeyState(vkeyCode) & 0x8000) != 0;
}

} // namespace dxutils

const float MathHelper::Infinity = FLT_MAX;
const float MathHelper::Pi = 3.1415926535f;

float MathHelper::AngleFromXY(float x, float y) {
  float theta = 0.0f;

  // Quadrant I or IV
  if (x >= 0.0f) {
    // If x = 0, then atanf(y/x) = +pi/2 if y > 0
    //                atanf(y/x) = -pi/2 if y < 0
    theta = atanf(y / x); // in [-pi/2, +pi/2]

    if (theta < 0.0f)
      theta += 2.0f * Pi; // in [0, 2*pi).
  }

  // Quadrant II or III
  else
    theta = atanf(y / x) + Pi; // in [0, 2*pi).

  return theta;
}

XMVECTOR MathHelper::RandUnitVec3() {
  XMVECTOR One = XMVectorSet(1.0f, 1.0f, 1.0f, 1.0f);
  XMVECTOR Zero = XMVectorZero();

  // Keep trying until we get a point on/in the hemisphere.
  while (true) {
    // Generate random point in the cube [-1,1]^3.
    XMVECTOR v = XMVectorSet(MathHelper::RandF(-1.0f, 1.0f),
        MathHelper::RandF(-1.0f, 1.0f), MathHelper::RandF(-1.0f, 1.0f), 0.0f);

    // Ignore points outside the unit sphere in order to get an even
    // distribution over the unit sphere.  Otherwise points will clump more on
    // the sphere near the corners of the cube.

    if (XMVector3Greater(XMVector3LengthSq(v), One))
      continue;

    return XMVector3Normalize(v);
  }
}

XMVECTOR MathHelper::RandHemisphereUnitVec3(XMVECTOR n) {
  XMVECTOR One = XMVectorSet(1.0f, 1.0f, 1.0f, 1.0f);
  XMVECTOR Zero = XMVectorZero();

  // Keep trying until we get a point on/in the hemisphere.
  while (true) {
    // Generate random point in the cube [-1,1]^3.
    XMVECTOR v = XMVectorSet(MathHelper::RandF(-1.0f, 1.0f),
        MathHelper::RandF(-1.0f, 1.0f), MathHelper::RandF(-1.0f, 1.0f), 0.0f);

    // Ignore points outside the unit sphere in order to get an even
    // distribution over the unit sphere.  Otherwise points will clump more on
    // the sphere near the corners of the cube.

    if (XMVector3Greater(XMVector3LengthSq(v), One))
      continue;

    // Ignore points in the bottom hemisphere.
    if (XMVector3Less(XMVector3Dot(n, v), Zero))
      continue;

    return XMVector3Normalize(v);
  }
}

ComPtr<ID3DBlob> d3dUtil::CompileShader(const std::string& filename,
    const D3D_SHADER_MACRO* defines, const std::string& entry_point,
    const std::string& target) {
  return dxutils::compile_shader(filename, defines, entry_point, target);
}

} // namespace ifire
